Repository navigation
Conversation
42e007b to
71db97f
Compare
60f62c4 to
1b36dc0
Compare
🔍 Adversarial Review — PR #4SummarySolid feature with thorough test coverage, but contains a confirmed race condition between Phase 1 and Phase 2 of 🐛 Bugs & Issues1. Phase 2 overwrites Phase 1 normalization for HTTP/HTTPS variants (Confirmed) When a schema declares
Reproduced locally with rdflib 7.x: from rdflib import Graph, Namespace, URIRef
g = Graph()
g.bind('schema', Namespace('http://schema.org/')) # metamodel
g.namespace_manager.bind('sdo', URIRef('https://schema.org/')) # user
# Phase 1
g.bind('schema', Namespace('https://schema.org/'), override=True, replace=True)
# → schema: https://schema.org/ ✅, schema1: http://schema.org/ (orphan)
# Phase 2
g.bind('schema', Namespace('http://schema.org/'), override=True, replace=True)
# → schema: http://schema.org/ ❌ (HTTPS binding silently lost!)Fix: Phase 2 should check whether # In Phase 2, before rebinding:
current_ns_for_std = dict(graph.namespaces()).get(std_pfx)
if current_ns_for_std and str(current_ns_for_std) != ns_str:
continue # std_pfx already claimed by Phase 1 for a different URI2. The test asserts
|
| PR #1 | PR #4 | |
|---|---|---|
| Approach | Dynamic: {str(ns): str(pfx) for pfx, ns in RdfGraph().namespaces()} |
Static: MappingProxyType({...}) with ~30 entries |
| rdflib dependency | Changes with rdflib version | Frozen, version-independent |
| HTTP/HTTPS variants | Only what rdflib provides | Explicitly includes both |
PR #4's static approach is explicitly designed to solve a problem with PR #1's dynamic approach (rdflib defaults changed between 6.x and 7.x). These will conflict on merge. Recommendation: merge PR #4 first, or rebase PR #1 to use PR #4's static map.
4. Cross-PR conflict — flag forwarding in jsonldgen.py
PR #4 forwards both normalize_prefixes AND deterministic to the embedded ContextGenerator:
for flag in ("normalize_prefixes", "deterministic"):
if hasattr(self, flag):
context_kwargs.setdefault(flag, getattr(self, flag))PR #1 adds deterministic handling in a different location (post-serialization in end_schema). PR #4's forwarding of deterministic creates a dependency on PR #1's field existing. The hasattr guard prevents a crash, but semantically PR #4 shouldn't forward a flag it doesn't define. Either:
- Remove
"deterministic"from the forwarding loop (PR feat(generators): add --normalize-prefixes for conventional prefix names in every generator #4 doesn't own this flag), or - Document the intentional coupling with PR feat(generators): add --deterministic flag with hybrid RDFC-1.0 + rdflib serialization #1 and establish merge order
5. PR title says fix: but commit says feat: — which is it?
The commit message uses feat(generators): while the PR title uses fix(generators):. This is a new opt-in flag, which is a feature — feat: is correct per conventional commits.
6. Static map maintenance burden
The _WELL_KNOWN_PREFIX_MAP needs manual updates when rdflib adds new curated defaults. The test_matches_rdflib_defaults test catches additions but not removals. Consider adding a reverse check: entries in the static map that are NOT in rdflib's defaults could indicate stale entries.
🧪 Test Coverage Assessment
Strong coverage (18 tests across 7 classes):
- ✅ OWL, SHACL, JSON-LD context normalization (sdo→schema, dce→dc)
- ✅ HTTP/HTTPS schema.org variants
- ✅ Collision detection with warnings (caplog)
- ✅ Cross-generator consistency
- ✅ Static map integrity and copy safety
- ✅ JSONLDGenerator flag forwarding
- ✅ Flag-off backward compatibility
Gaps:
- ❌ No test for
normalize_prefixes+exclude_importsinteraction - ❌ No test for empty schema (zero prefixes) — is
_prefix_remap.clear()safe? - ❌ No test that explicitly sets up both HTTP and HTTPS runtime bindings to exercise Phase 2 overwrite (the scenario from Bug feat(generators): add --deterministic flag with hybrid RDFC-1.0 + rdflib serialization #1)
- ❌ No test for
add_prefixbeing called before__post_init__completes (defensive, but_prefix_remapaccess would crash) - ❌ CURIE remapping test (
test_normalize_prefixes_curie_remapping) only checks thatPerson @idstarts withschema:— doesn't verifyfull_nameslot's@idusesschema:nameinstead ofsdo:name
📋 Fix Plan
- Fix Phase 2 overwrite bug — add a graph-binding check before rebinding in Phase 2 to prevent overwriting Phase 1's normalization
- Add regression test — explicitly set up a graph with both
schema: http://schema.org/andsdo: https://schema.org/, run normalize, and assert the HTTPS binding survives - Remove
"deterministic"from forwarding loop — or gate it behindhasattronly (already done) but add a comment explaining the cross-PR dependency - Fix PR title — change
fix:tofeat:for consistency with the commit - Add empty-schema test — verify no crash when schema has zero prefixes
- Add
exclude_importsinteraction test — verify normalization works when imports are excluded - Strengthen CURIE remapping test — assert that slot URIs (not just class URIs) are remapped in the context
✅ What's Good
- Excellent documentation: the 3-case handling in
visit_schemais well-commented with case numbers - Defensive
_prefix_remapinitialization: correctly placed beforesuper().__post_init__()with clear comment explaining why - Collision detection: warning on skip is the right behavior — no silent data loss
MappingProxyTypefor the static map: prevents accidental mutationwell_known_prefix_map()returns a copy: callers can't corrupt the canonical map- Phase 2 concept is sound: cleaning up runtime-injected orphan bindings is the right idea, it just needs the overwrite guard
- Cross-generator consistency test: ensures OWL, SHACL, and JSON-LD agree — excellent
- Backward compatibility: flag defaults to off, existing behavior preserved
d9fb738 to
dd0ce0c
Compare
8d098e9 to
ef47b47
Compare
8dc3ece to
260a97e
Compare
260a97e to
e56ca65
Compare
e56ca65 to
5f17e19
Compare
ce55ccb to
909eb7d
Compare
a4b329c to
32821d0
Compare
32821d0 to
0342283
Compare
Review update from #4: test_csv_limit left the process-wide CSV field size limit at 20, so later tests on the same xdist worker that read prefixmaps' CSV data failed intermittently. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
0342283 to
316b430
Compare
Review update from #4: test_csv_limit left the process-wide CSV field size limit at 20, so later tests on the same xdist worker that read prefixmaps' CSV data failed intermittently. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
9ede52d to
29098c8
Compare
…mes in every generator Add an opt-in --normalize-prefixes option that writes the conventional name of a well-known namespace, e.g. dc for http://purl.org/dc/elements/1.1/ where a schema declares dce, and writes the same name in every generator that prints prefix names: gen-owl, gen-shacl, gen-shex, gen-sparql, gen-yarrrml, gen-sssom and gen-jsonld-context. Only names change; every IRI stays the same. The names come from two curated sources, the prefixmaps linked_data context and the names rdflib binds by default, which take precedence because rdflib serializes the RDF outputs. prefixmaps' merged context is not used: it names a namespace after the first registry that lists it, such as alice for http://example.org/ from prefix.cc. linkml.generators.common.prefixes computes one plan per schema as a curies Converter of preferred names with the declared names as synonyms. A name that the schema or one of its imports declares keeps denoting its namespace, since LinkML expands the CURIEs of data with all of them; linkml:types declares schema for http://schema.org/, so a schema's sdo for https://schema.org/ keeps its name. A prefix also keeps its name, with a warning, where two namespaces want the same conventional name or the name is that of a class, slot or permissible value, which a JSON-LD context uses as a term. How a generator applies the plan depends on what its prefixes are. Self-contained documents (Turtle, RDF/XML, ShExC, SPARQL, YARRRML, SSSOM) use only the preferred names, as a prefix there only abbreviates IRIs (RDF 1.1 Concepts 1.4); they rename only the prefixes they declare, and not to a name they write undeclared. A JSON-LD context is shared with the data written against it, and a compact IRI whose prefix it no longer defines stops expanding (JSON-LD 1.1 4.1.5), so it keeps every declared name and adds the preferred one; it writes its own compact IRIs, but not its keys, with the preferred names, and renames nothing with --no-prefixes. SchemaLoader-based generators read the plan from a SchemaView of the schema as given, resolving local imports of YAML text against base_dir. gen-prefix-map gives each namespace one name, the schema's, gen-rdf and gen-jsonld write CURIEs as literal data, and gen-python, gen-pydantic and gen-namespaces make prefix names part of the generated code, so they do not offer the option. test_csv_limit set csv.field_size_limit(20) without restoring it, so any later test on the same worker that read prefixmaps' CSV data failed; it now restores the limit. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
29098c8 to
d6e7eae
Compare
Mirror of linkml#3446, same head. Review there; this PR tracks the fork stack.
Adds an opt-in
--normalize-prefixesoption that writes the conventional name of a well-known namespace, the same in every generator that prints prefix names. Only names change; every IRI stays the same.Example
--normalize-prefixesdce:title,w3prov:Agentdc:title,prov:Agentdce,w3provdce,w3provanddc,prov; writesdc:titleex(no conventional name)exexWhy the two groups differ
"source": "dce:x"would stop expanding. It keeps every declared name and adds the conventional one; data may use either.Where the names come from
linked_datacontext and the names rdflib binds by default (rdflib serializes the RDF outputs, so its names win; the two agree on every namespace both name). This goes through prefixmaps, as asked in review.mergedcontext is not used: it names a namespace after the first registry that lists it (aliceforhttp://example.org/from prefix.cc,ADMSfrom bioregistry). The earlier git pin on prefixmaps is gone; released prefixmaps (≥ 0.2.2) works.One plan per schema
linkml.generators.common.prefixes.preferred_prefixes(sv)returns a curiesConverter: the preferred name per namespace, the declared names as synonyms. Every generator applies the same plan. A prefix keeps its declared name, with a warning, when the conventional namelinkml:types'schema(forhttp://schema.org/) stayshttp://schema.org/'s, and a schema'ssdoforhttps://schema.org/keeps its name;timeblocksowltime→time).Per generator
schema: http://schema.org/also wins over rdflib's defaultschema→ https, so noschema1.skos:exactMatchin SSSOM).@type;--no-prefixesrenames nothing, since its prefixes then come from another context.curiesrejects synonyms there), gen-rdf and gen-jsonld (CURIEs are literal data), gen-python, gen-pydantic, gen-namespaces (prefix names are part of the generated code). To change the names users write, rename the prefixes in the schema.Testing
test_normalize_prefixes.py(527 tests) over 15 schemas with hard cases (both schema.org variants, imported names in use or unused,dc/dce, case-only alias, a slot or enum value named like a prefix, alias asdefault_prefix, in a typeurior enum meaning, prefixes declared only in an import, a declaredschema: http://schema.org/):--diff-stable, mapped URIs, metaclasses, metadata profile, annotations), ShExJ, SPARQL parse trees with expanded names, YARRRML and SSSOM with expanded CURIEs; and they use the planned names;--use-curies,--flatprefixes,--fix-multivalue-containers,--no-mergeimports); data using any prefix of the schema or its imports expands the same, or, for an imported prefix only the normalized context defines, to the namespace LinkML uses; data written with the planned names means the same;--no-model+--no-prefixescontexts, gen-prefix-map +--no-prefixes, an importer with--exclude-importsnext to its import's context, frames, YAML text input with local imports;--no-normalize-prefixesequals the default, rejected elsewhere.Ten mutations of the plan and the generators (closure prefixes, ownership, blocked names, kept aliases,
replace=True, keys, SSSOM columns, the undeclared-name guard, YAML text) each fail tests.tests/linkml, the slow suite andtests/linkml_runtimepass.test_csv_limitnow restorescsv.field_size_limit: left at 20, it broke prefixmaps' CSV loading in later tests on the same xdist worker.Limitations
Checklist